package top.codedance.iotp.client.plugins.async.system;

import com.alibaba.fastjson.JSONObject;
import top.codedance.iotp.client.ClientApplication;
import top.codedance.iotp.common.executer.AsyncAbstractExecuter;
import top.codedance.iotp.common.constent.Command;
import top.codedance.iotp.common.entity.CommonEntity;
import top.codedance.iotp.common.util.SystemUtil;
import com.sshtools.forker.client.ForkerBuilder;
import com.sshtools.forker.client.PowerShellBuilder;
import com.sshtools.forker.wrapper.ForkerWrapper;
import com.sshtools.forker.wrapper.WrapperIO;
import com.sshtools.forker.wrapper.WrapperProcessFactory;
import org.apache.commons.lang3.SystemUtils;
import org.apache.log4j.Logger;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SystemService implements Runnable {
    private static Logger logger = ClientApplication.getLogger(SystemService.class);
    private static Pattern pingPattern = Pattern.compile(".*ping\\s*$", Pattern.DOTALL | Pattern.MULTILINE);
    public static volatile Map<String, SystemService> services = new HashMap<>();
    private final String ENTER_CHART = "\n";
    private AsyncAbstractExecuter executer;
    private String clientId;
    private JSONObject body;
    private Process p;
    private OutputStream output;
    private InputStream input;
    private BufferedReader reader;
    private BufferedWriter writer;
    private boolean isRunning = true;
    private long lastInteractiveTimestamp = 0;
    private final long maxActiveTime = 1000 * 60 * 10;
    static {
        Timer timer = new Timer();
        timer.schedule(new TimerTask(){
            @Override
            public void run() {
                synchronized (SystemService.class) {
                    try {
                        Set<Map.Entry<String, SystemService>> entries = services.entrySet();
                        for (Map.Entry<String, SystemService> entry : entries) {
                            if (!entry.getValue().isRunning) {
                                services.remove(entry.getKey());
                            }
                        }
                        logger.debug("System service instancer count: " + services.size());
                    }catch (Exception e){}
                }
            }
        }, 1000, 1000);
    }
    private SystemService(AsyncAbstractExecuter executer){
        this.executer = executer;
        this.clientId = executer.getClientId();
        this.body = executer.getRequestBodyJSONObject();
    }

    @Override
    public void run() {
        init();
    }

    private void init(){
        if(this.body.containsKey("body")) {
            try {
                JSONObject b = this.body.getJSONObject("body");
                boolean isPowershell = false;
                try{
                    isPowershell = b.getBoolean("powershell");
                }catch (Exception e){}
                ForkerBuilder builder;
                if(isPowershell){
                    builder = new PowerShellBuilder();
                }else {
                    builder = new ForkerBuilder();
                }
                builder.directory(new File(SystemUtil.getRuntimeDirPath(ClientApplication.class))).io(WrapperIO.WRAPPER).redirectErrorStream(true);
                WrapperProcessFactory processFactory = builder.configuration().processFactory(WrapperProcessFactory.class);
                processFactory.addOption(new ForkerWrapper.KeyValuePair("native", "true"));
                processFactory.addOption(new ForkerWrapper.KeyValuePair("level", "ALL"));
                processFactory.addOption(new ForkerWrapper.KeyValuePair("exit-wait", "0"));
                if (SystemUtils.IS_OS_LINUX || SystemUtils.IS_OS_MAC) {
                    builder.command("sh", "-i");
                    p = builder.start();
                } else if (SystemUtils.IS_OS_WINDOWS) {
                    if(!isPowershell) {
                        builder.command("cmd");
                    }
                    p = builder.start();
                } else {
                    send("Unsupported this platform.");
                }
                if(p != null) {
                    output = p.getOutputStream();
                    input = p.getInputStream();
                    reader = new BufferedReader(new InputStreamReader(input, System.getProperty("sun.jnu.encoding")));
                    if (reader != null) {
                        SystemExecuterImpl.pools.submit(() -> {
                            Matcher matcher;
                            while (isRunning) {
                                try {
                                    String line;
                                    while (isRunning && (line = reader.readLine()) != null) {
                                        matcher = pingPattern.matcher(line);
                                        if(!matcher.find() && !line.equals("")) {
                                            JSONObject jo = new JSONObject();
                                            jo.put("data", line);
                                            send(jo.toJSONString());
                                            logger.debug(jo.toJSONString());
                                            lastInteractiveTimestamp = getTimestamp();
                                        }
                                    }
                                } catch (Exception e) {
                                    logger.debug(e.getMessage());
                                }
                                try {
                                    Thread.sleep(100);
                                } catch (Exception e) { }
                            }
                        });
                    }
                    writer = new BufferedWriter(new OutputStreamWriter(output, System.getProperty("sun.jnu.encoding")));
                    lastInteractiveTimestamp = getTimestamp();
                    SystemExecuterImpl.pools.submit(() -> {
                        long t;
                        while (isRunning){
                            t = getTimestamp() - lastInteractiveTimestamp;
                            logger.debug("Time difference: " + clientId + " : " + t);
                            if(t >= maxActiveTime){
                                isRunning = false;
                                p.destroyForcibly();
                            }
                            try {
                                Thread.sleep(5000);
                                writer.write("echo ping" + ENTER_CHART);
                                writer.flush();
                            } catch (Exception e) { }
                        }
                    });
                    logger.debug("return status: (" + p.waitFor() + ")");
                }
            } catch (Exception ex) {
                isRunning = false;
                send("Execute command fail.");
                logger.debug(ex.getMessage());
            } finally {
                isRunning = false;
                try {
                    reader.close();
                    reader = null;
                    input.close();
                    input = null;
                    writer.close();
                    writer = null;
                    output.close();
                    output = null;
                    p.destroy();
                    p = null;
                } catch (Exception ex) { }
            }
        }
    }

    public void exec(JSONObject request) {
        if(request.containsKey("body")) {
            if(writer != null) {
                JSONObject b = request.getJSONObject("body");
                String cmd = b.getString("command");
                try {
                    writer.write(cmd + ENTER_CHART);
                    writer.flush();
                    lastInteractiveTimestamp = getTimestamp();
                } catch (Exception e) {
                    logger.debug(e.getMessage());
                }
            }
        }
        if(request.containsKey("release")) {
            isRunning = false;
            p.destroyForcibly();
        }
    }

    /**
     * 获取实例
     * @param executer
     * @return
     */
    public static synchronized SystemService genOrGetInstance(SystemExecuterImpl executer){
        String clientId = executer.getClientId();
        if(services.containsKey(clientId)){
            return services.get(clientId);
        }else{
            SystemService systemService = new SystemService(executer);
            SystemExecuterImpl.pools.submit(systemService);
            services.put(clientId, systemService);
            return systemService;
        }
    }

    /**
     * 向客户端发送内容
     * @param body
     */
    private void send(String body){
        CommonEntity ce = new CommonEntity();
        ce.setCommand(Command.ASYNC_SYSTEM_COMMAND_RECEIVE);
        ce.setBody(body.getBytes(StandardCharsets.UTF_8));
        this.executer.send(ce.build());
    }

    /**
     * 获取当前时间戳
     * @return
     */
    private long getTimestamp(){
        Date d = new Date();
        return d.getTime();
    }
}
